package contact

import (
	"common"
	"common/ipv6"
	"common/version"
	"common/ws_contact"
	"common/ws_contact/reconnecting"
	"context"
	"enode/db"
	"enode/status"
	"github.com/shirou/gopsutil/v3/disk"
	"go.uber.org/zap"
	"log"
	"os"
	"path"
	"runtime"
	"sync"
	"sync/atomic"
	"time"
)

type Config struct {
	HeartbeatAddr     string        `help:"卫星心跳连接地址"  devDefault:"wss://estore.guanjieai.com:10002/heartbeat" default:"wss://estore.guanjieai.com:10002/heartbeat"`
	HeartbeatInternal time.Duration `help:"心跳间隔" releaseDefault:"10s" default:"10s"`
	NodeId            string        `internal:"true"`
	DevId             string        `internal:"true"`
}

type Service struct {
	log       *zap.Logger
	config    *Config
	mux       sync.Mutex
	endpoint  ws_contact.ContactEnode
	db        *db.DB
	piecePath string
	nat       int8
	ws        *reconnecting.Wsc
}

func NewService(log *zap.Logger, db *db.DB, cfg Config, endpoint ws_contact.ContactEnode, piecePath string) *Service {
	return &Service{
		log:       log,
		config:    &cfg,
		endpoint:  endpoint,
		piecePath: piecePath,
		db:        db,
	}
}
func (s *Service) CheckIn(req ws_contact.CheckInRequest) (err error) {
	data, err := ws_contact.PackMsgCheckIn(req)
	if err != nil {
		return err
	}
	return s.ws.SendBinaryMessage(data)
}

func (s *Service) Report(req ws_contact.ReportRequest) error {
	data, err := ws_contact.PackMsgReport(req)
	if err != nil {
		return err
	}
	return s.ws.SendBinaryMessage(data)
}

func (s *Service) Run(ctx context.Context) error {
	ws := reconnecting.New(s.config.HeartbeatAddr, s.config.NodeId)
	s.ws = ws

	var isConnected atomic.Bool

	ws.OnConnected(func() {
		log.Println("OnConnected: ", ws.WebSocket.Url)
		isConnected.Store(true)
		// 连接成功后，测试每5秒发送消息
		//go func() {
		//
		//}()
	})
	ws.OnConnectError(func(err error) {
		//log.Println("OnConnectError: ", err.Error())
		s.log.Error("OnConnectError", zap.Error(err))
		isConnected.Store(false)
	})
	ws.OnDisconnected(func(err error) {
		//log.Println("OnDisconnected: ", err.Error())
		s.log.Error("OnDisconnected", zap.Error(err))
	})
	ws.OnClose(func(code int, text string) {
		log.Println("OnClose: ", code, text)
		s.log.Error("OnClose")
	})
	ws.OnTextMessageSent(func(message string) {
		//log.Println("OnTextMessageSent: ", message)
	})
	ws.OnBinaryMessageSent(func(data []byte) {
		//log.Println("OnBinaryMessageSent: ", string(data))
	})
	ws.OnSentError(func(err error) {
		//log.Println("OnSentError: ", err.Error())
		s.log.Error("OnSentError", zap.Error(err))
	})
	ws.OnPingReceived(func(appData string) {
		//log.Println("OnPingReceived: ", appData)
	})
	ws.OnPongReceived(func(appData string) {
		log.Println("OnPongReceived: ", appData)
	})
	ws.OnTextMessageReceived(func(message string) {
		log.Println("OnTextMessageReceived: ", message)
	})
	ws.OnBinaryMessageReceived(func(data []byte) {
		log.Println("OnBinaryMessageReceived: ")
	})
	// 开始连接
	go ws.Connect()
	t := time.NewTicker(10 * time.Second)
	t2 := time.NewTicker(10 * time.Minute)
	defer func() {
		t.Stop()
		t2.Stop()
	}()
	for {
		for {
			select {
			case <-t.C:
				//s.log.Debug("GetIpv6", zap.String("", addr))
				//在回调中判断是否成功
				if isConnected.Load() {
					s.CheckIn(ws_contact.CheckInRequest{
						NodeId:  s.config.NodeId,
						DevId:   s.config.DevId,
						Version: getVersion(),
						Nat:     5,
						Region:  "sz",
						Ip:      ipv6.GetIPV4(),
						Ipv6:    ipv6.GetIPV6(),
						Port:    18081,
					})
				}
			case <-t2.C:
				if isConnected.Load() {
					s.reportDiskSpace()
				}
			case <-ctx.Done():
				return nil
			}
		}
	}
}

// reportDiskSpace上报硬盘空间
func (s *Service) reportDiskSpace() {
	var total, free, dataTotal int64
	if runtime.GOOS == "darwin" {
		total = 1000
		free = 999
		dataTotal = 0
	} else {
		diskSpace, err := disk.Usage(s.piecePath)
		if err != nil {
			s.log.Error("读硬盘空间失败", zap.String("root path", s.piecePath), zap.Error(err))
		}
		total = int64(diskSpace.Total)
		free = int64(diskSpace.Free)
		dataTotal = status.GetDirUse(s.piecePath)
	}
	s.log.Info("硬盘空间", zap.Int64("total(G)", total/1e9), zap.Int64("free(G)", free/1e9), zap.Int64("data(KB)", dataTotal/1024))
	s.Report(ws_contact.ReportRequest{
		NodeId:     s.config.NodeId,
		FreeDisk:   free,
		TotalDisk:  total,
		DataTotal:  dataTotal,
		CpuPercent: 0,
		MemFree:    0,
		PieceCount: 0,
	})
}

func clearDir(dir string) {
	if common.IsForbitDir(dir) {
		return
	}
	//todo 这个方法删除大量文件可能会卡死。
	if entrys, err := os.ReadDir(dir); err == nil {
		for _, entry := range entrys {
			if !entry.IsDir() {
				os.Remove(path.Join(dir, entry.Name()))
			}
		}
	}
}

func getVersion() string {
	ver := "v0.0.0"
	if version.Build.Version != "" {
		ver = version.Build.Version
	}
	return ver
}

// cleanPiece 删除所有piece , todo 实现文件删除后删除这个函数。
//func (s *Service) CleanPiece() {
//	go func() {
//		dir := s.piecePath
//		clearDir(dir)
//	}()
//}
